import { pathOr } from 'ramda';
import { warnEventId } from '../../../../common/warnId';
import errorCodesBuilder from '../../../helpers/errorCodesBuilder';

const errorCodes = errorCodesBuilder('资产变动结果处理失败');

export default async function asset({
	log,
	Asset,
	assetId,
	target,
	taskSettings,
	response: {
		port: ipPorts = [],
		subdomain: subDomains = [],
		ip2domain: ipBindDomains = [],
	},
}) {
	const domain = new URL(target).hostname;
	const ret = {
		ipBindDomains: ipBindDomains?.data?.length
			? [{ ip: ipBindDomains.ip, domanis: ipBindDomains.data }]
			: [],
		subDomains: subDomains
			.filter(x => (x?.host || '').endsWith(`.${domain}`))
			.map(({ host, ip, cname }) => ({
				host,
				cname,
				point: ip === '-' ? '' : ip,
			})),
		ipPorts: [{ ip: ipPorts.ip, ports: ipPorts.data }],
	};

	try {
		await Asset.updateOne(
			{ _id: assetId },
			{
				$set: { ...ret, updateTime: Date.now() },
			}
		);
	} catch (err) {
		const error = errorCodes['[02]资产监测Worker更新资产失败'].from(err);
		log.error({ error, taskSettings }, error.rawMessage);
	}

	let asset;
	try {
		asset = await Asset.findOne({ _id: assetId }).populate('resultId').lean();
	} catch (err) {
		const error = errorCodes['[02]监测结果处理：关联Mongo记录查询失败'].from(
			err
		);
		log.error({ error }, error.rawMessage);
		throw error;
	}
	ret.warnings = await warningsCheck({ log, asset, result: ret });
	ret.stats = {
		subdomainCount: 0,
		ipCount: 0,
		portCount: 0,
		ipBindDomainCount: 0,
		addSubdomainCount: 0,
		addIpCount: 0,
		addPortCount: 0,
		addIpBindDomainCount: 0,
		removeSubdomainCount: 0,
		removeIpCount: 0,
		removePortCount: 0,
		removeIpBindDomainCount: 0,
		changePortServiceCount: 0,
	};
	ret.stats.subdomainCount = ret.subDomains.length;
	ret.stats.ipCount = ret.ipPorts.length;
	ret.stats.portCount = ret.ipPorts.reduce(
		(count, x) => count + x.ports?.length || 0,
		0
	);
	ret.stats.ipBindDomainCount = ret.ipBindDomains.reduce((set, record) => {
		(record?.domains || []).filter(Boolean).forEach(x => set.add(x.domain));
		return set;
	}, new Set()).size;
	ret.warnings.reduce((obj, x) => {
		let key;
		if (x.category === 'portService') {
			key = 'changePortServiceCount';
		} else {
			const category = x.category[0].toUpperCase() + x.category.slice(1);
			const op = x.detail.op;
			key = `${op}${category}Count`;
		}
		obj[key] += 1;

		return obj;
	}, ret.stats);

	return ret;
}

async function warningsCheck({ asset, result: { subDomains, ipPorts } }) {
	if (!asset?.resultId?.result) return [];
	const prevResult = asset.resultId.result;

	const warnings = [];

	const subdomainChanges = changesCompare(
		pathOr([], ['subDomains'], prevResult).map(x => x.host),
		subDomains.map(x => x.host)
	);
	subdomainChanges.add.forEach(domain => {
		warnings.push({
			id: warnEventId({
				taskType: 'asset',
				id: 'subdomain',
				affects: domain,
			}),
			level: 1,
			title: `[${domain}]子域名上线`,
			category: 'subdomain',
			affects: domain,
			detail: { op: 'add' },
		});
	});
	subdomainChanges.remove.forEach(domain => {
		warnings.push({
			id: warnEventId({
				taskType: 'asset',
				id: 'subdomain',
				affects: domain,
			}),
			level: 1,
			title: `[${domain}]子域名下线`,
			category: 'subdomain',
			affects: domain,
			detail: { op: 'remove' },
		});
	});

	const portChanges = changesCompare(
		pathOr([], ['ipPorts', 0, 'ports'], prevResult).map(x => x.port),
		pathOr([], [0, 'ports'], ipPorts).map(x => x.port)
	);
	portChanges.add.forEach(port => {
		warnings.push({
			id: warnEventId({ taskType: 'asset', id: 'port', affects: port }),
			level: 1,
			title: `[${port}]端口开放`,
			category: 'port',
			affects: port,
			detail: { op: 'add' },
		});
	});
	portChanges.remove.forEach(port => {
		warnings.push({
			id: warnEventId({ taskType: 'asset', id: 'port', affects: port }),
			level: 1,
			title: `[${port}]端口关闭`,
			category: 'port',
			affects: port,
			detail: { op: 'remove' },
		});
	});

	if (portChanges.stay.length) {
		const currentPortServiceMap = pathOr([], [0, 'ports'], ipPorts).reduce(
			(obj, x) => {
				obj[x.port] = x.service || '未知服务';
				return obj;
			},
			{}
		);
		const prevPortServiceMap = pathOr(
			[],
			['ipPorts', 0, 'ports'],
			prevResult
		).reduce((obj, x) => {
			obj[x.port] = x.service || '未知服务';
			return obj;
		}, {});
		portChanges.stay.forEach(port => {
			if (prevPortServiceMap[port] === currentPortServiceMap[port]) return;
			warnings.push({
				id: warnEventId({
					taskType: 'asset',
					id: 'portService',
					affects: port,
				}),
				level: 1,
				title: `端口服务${port}(${prevPortServiceMap[port]})调整为${port}(${currentPortServiceMap[port]})`,
				category: 'portService',
				affects: port,
				detail: {
					prev: prevPortServiceMap[port],
					current: currentPortServiceMap[port],
				},
			});
		});
	}

	return warnings;
}

export function changesCompare(prev, next) {
	const removeMap = prev.reduce((obj, x) => {
		obj[x] = true;
		return obj;
	}, {});
	const nextMap = next.reduce((obj, x) => {
		obj[x] = true;
		return obj;
	}, {});

	const addMap = {};
	const stayMap = {};
	Object.keys(nextMap).forEach(key => {
		if (!removeMap[key]) {
			addMap[key] = true;
		} else {
			stayMap[key] = true;
		}
		delete removeMap[key];
	});

	return {
		add: Object.keys(addMap),
		remove: Object.keys(removeMap),
		stay: Object.keys(stayMap),
	};
}
